///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_MAT_DETAIL_CSTORAGE_H_
#define _BUOLA_MAT_DETAIL_CSTORAGE_H_

#include <buola/utility/ustorage.h>
#include <buola/mat/detail/csizeinfo.h>

namespace buola { namespace mat { namespace detail {

template<size_t vRows,size_t vCols>
struct MStorageSize
{
    static const size_t cSize=(vRows==0||vCols==0)?0:
                        ((vRows==cDynamic||vCols==cDynamic||vCols*vRows>40)?cDynamic:vRows*vCols);
};

template<typename tScalar,size_t vRows,size_t vCols,bool vFixedSize=(vRows!=cDynamic&&vCols!=cDynamic)>
class CStorage : private CSizeInfo<vRows,vCols>
               , private UStorage<tScalar,MStorageSize<vRows,vCols>::cSize>
{
    typedef UStorage<tScalar,MStorageSize<vRows,vCols>::cSize> TStorageBase;
    typedef CSizeInfo<vRows,vCols> TSizeInfo;

public:
    CStorage()=default;

    CStorage(size_t pRowsOrCols)
        :   TSizeInfo(pRowsOrCols)
        ,   TStorageBase(Rows()*Cols())
    {}

    CStorage(size_t pRows,size_t pCols)
        :   TSizeInfo(pRows,pCols)
        ,   TStorageBase(Rows()*Cols())
    {}

    CStorage(const CStorage &pRH)
        :   TSizeInfo(pRH)
        ,   TStorageBase(pRH,Rows()*Cols())
    {}

    CStorage(CStorage&& pRH)=default;

    using TSizeInfo::Rows;
    using TSizeInfo::Cols;
    using TSizeInfo::size;

    using TStorageBase::Data;
    using TStorageBase::At;
    
    size_t Stride() const {   return Cols()*sizeof(tScalar);  }

    CStorage &operator=(const CStorage &pRH)
    {
        TSizeInfo::operator=(pRH);
        TStorageBase::Assign(pRH,size());
        return *this;
    }
    
    CStorage &operator=(CStorage &&pRH)
    {
        TSizeInfo::operator=(pRH);
        TStorageBase::Assign(std::move(pRH));
        return *this;
    }

    tScalar &At(size_t r,size_t c)      {   return this->At(r*Cols()+c); }
    tScalar At(size_t r,size_t c) const {   return this->At(r*Cols()+c); }

    void Resize(size_t pRows,size_t pCols)
    {
        if(pRows==Rows()&&pCols==Cols()) return;

        TSizeInfo::Resize(pRows,pCols);
        TStorageBase::Resize(Rows()*Cols());
    }
    
    void Resize(size_t pLength)
    {
        static_assert(vRows==1||vCols==1,"only vectors can be resized with a single parameter");

        if(vRows==1)
            Resize(1,pLength);
        else
            Resize(pLength,1);
    }

    void Shrink(size_t pRows,size_t pCols)
    {
        assert(pRows<=Rows()&&pCols<=Cols());
        TSizeInfo::Resize(pRows,pCols);
    }
};

template<typename tScalar,size_t vRows,size_t vCols>
class CStorage<tScalar,vRows,vCols,true> : private CSizeInfo<vRows,vCols>
                                         , private UStorage<tScalar,MStorageSize<vRows,vCols>::cSize>
{
    typedef UStorage<tScalar,MStorageSize<vRows,vCols>::cSize> TStorageBase;
    typedef CSizeInfo<vRows,vCols> TSizeInfo;

public:
    CStorage()=default;

    CStorage(size_t pRowsOrCols)
        :   TSizeInfo(pRowsOrCols)
        ,   TStorageBase(Rows()*Cols())
    {}

    CStorage(size_t pRows,size_t pCols)
        :   TSizeInfo(pRows,pCols)
        ,   TStorageBase(Rows()*Cols())
    {}

    CStorage(const CStorage &pRH)=default;
    CStorage(CStorage&& pRH)=default;

    using TSizeInfo::Rows;
    using TSizeInfo::Cols;
    using TSizeInfo::size;

    using TStorageBase::Data;
    using TStorageBase::At;

    constexpr size_t Stride() const {   return Cols()*sizeof(tScalar);  }

    CStorage &operator=(const CStorage &pRH)
    {
        TSizeInfo::operator=(pRH);
        TStorageBase::Assign(pRH,size());
        return *this;
    }
    
    CStorage &operator=(CStorage &&pRH)
    {
        TSizeInfo::operator=(pRH);
        TStorageBase::Assign(std::move(pRH));
        return *this;
    }

    tScalar &At(size_t r,size_t c)      {   return this->At(r*Cols()+c); }
    tScalar At(size_t r,size_t c) const {   return this->At(r*Cols()+c); }

    void Resize(size_t pRows,size_t pCols)
    {
        if(pRows==Rows()&&pCols==Cols()) return;

        TSizeInfo::Resize(pRows,pCols);
        TStorageBase::Resize(Rows()*Cols());
    }
};

/*namespace detail*/ } /*namespace mat*/ } /*namespace buola*/ }

#endif
